﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;

using Ordinaire;
using Ordinaire.Db;
using Ordinaire.Taxonomy.Data;

namespace Ordinaire.Taxonomy
{
    /// <summary>
    /// Creates a hierarchical classification of keywords/vocabularies.
    /// </summary>
    public class Taxonomist : BusinessManager<HierarchyDataManager<OContext>, OContext>
    {
        #region Variables

        /// <summary>
        /// Reference to collection of Vocabularies in data source.
        /// </summary>
        private Vocabularies vocabularies;

        #endregion

        #region Constructor

        /// <summary>
        /// Returns new instance of ClickPOS.Taxonomy.HierarchyManager.
        /// </summary>
        public Taxonomist()
        {
            dataManager  = new HierarchyDataManager<OContext>();
            vocabularies = new Vocabularies();
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Assigns specified keywords as root nodes in hierarchy.
        /// </summary>
        /// <param name="keywords">the keywords as roots</param>
        /// <returns>the list of generated ids upon insertion into the data source</returns>
        public int[] Add(params string[] keywords)
        {
            return Add<Taxonomies>(keywords);
        }

        /// <summary>
        /// Assigns specified keywords as root nodes in hierarchy.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keywords">the keywords as roots</param>
        /// <returns>the list of generated ids upon insertion into the data source</returns>
        public int[] Add<TTable>(params string[] keywords)
            where TTable : Taxonomies
        {
            keywords.ThrowIfNull<ArgumentNullException>("keywords");
            keywords.ThrowIfEmpty<ArgumentException>("keywords is empty");

            using (TransactionScope transaction = new TransactionScope())
            {
                string keyword = String.Empty;
                List<int> ids = new List<int>();

                for (int i = 0; i < keywords.Length; i++)
                {
                    keyword = keywords[i];
                    Vocab vocab = vocabularies.Create(keyword);

                    // share same DataContext to prevent the need of activating MSDTC during TransactionScope.
                    dataManager.Context = vocabularies.Context;

                    // root with specified keyword already exists
                    if (RootExists(keyword))
                    {
                        continue;
                    }

                    TTable taxonomiesData = typeof(TTable).Instance<TTable>();
                    taxonomiesData.Id = vocab.Id;
                    dataManager.Insert<TTable, int, string>(taxonomiesData);

                    ids.Add(taxonomiesData.RowId);
                }

                transaction.Complete();
                return ids.ToArray();
            }
        }

        /// <summary>
        /// Appends the specified keywords as child nodes of specified parentKeyword.
        /// </summary>
        /// <param name="parentKeyword">the parent keyword</param>
        /// <param name="keywords">the new keywords as child</param>
        /// <returns>the list of generated ids upon insertion into the data source</returns>
        public int[] AddChildren(string parentKeyword, params string[] keywords)
        {
            return AddChildren<Taxonomies>(parentKeyword, keywords);
        }

        /// <summary>
        /// Appends the specified keywords as child nodes of specified parentKeyword.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="parentKeyword">the parent keyword</param>
        /// <param name="keywords">the new keywords as child</param>
        /// <returns>the list of generated ids upon insertion into the data source</returns>
        public int[] AddChildren<TTable>(string parentKeyword, params string[] keywords)
            where TTable : Taxonomies
        {
            parentKeyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "parentKeyword"));
            keywords.ThrowIfNull<ArgumentNullException>("keywords");
            keywords.ThrowIfEmpty<ArgumentException>("keywords is empty");

            // Check parent keywords
            Vocab parentVocab = vocabularies.Create(parentKeyword);
            if (!Exists(parentVocab))
            {
                throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.NotExists, String.Format("parentKeyword '{0}'", parentKeyword));
            }

            using (TransactionScope transaction = new TransactionScope())
            {
                string keyword = String.Empty;
                List<int> ids = new List<int>();

                for (int i = 0; i < keywords.Length; i++)
                {
                    keyword = keywords[i];
                    Vocab vocab = vocabularies.Create(keyword);

                    // share same DataContext to prevent the need of activating MSDTC during TransactionScope.
                    dataManager.Context = vocabularies.Context;

                    // skip if link with specified parent keyword and keyword already exists
                    if (Exists(vocab, parentVocab))
                    {
                        continue;
                    }

                    Taxonomies taxonomiesData = new Taxonomies();
                    taxonomiesData.Id         = vocab.Id;
                    taxonomiesData.ParentId   = parentVocab.Id;
                    dataManager.Insert<Taxonomies, int, string>(taxonomiesData);

                    ids.Add(taxonomiesData.RowId);
                }

                transaction.Complete();
                return ids.ToArray();
            }
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword exists in the data source.
        /// </summary>
        /// <param name="keyword">the keyword to search for</param>
        /// <returns>true if keyword exists in the data source; otherwise false</returns>
        public bool Exists(string keyword)
        {
            return Exists<Taxonomies>(keyword);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab exists in the data source.
        /// </summary>
        /// <param name="vocab">the vocab to search for</param>
        /// <returns>true if vocab exists in the data source; otherwise false</returns>
        public bool Exists(Vocab vocab)
        {
            return Exists<Taxonomies>(vocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword exists in the data source.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword to search for</param>
        /// <returns>true if keyword exists in the data source; otherwise false</returns>
        public bool Exists<TTable>(string keyword)
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));

            Vocab vocab = vocabularies.Create(keyword);
            return Exists<TTable>(vocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab exists in the data source.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the vocab to search for</param>
        /// <returns>true if vocab exists in the data source; otherwise false</returns>
        public bool Exists<TTable>(Vocab vocab)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));

            return (dataManager.Count<TTable>(vocab.Id) > 0);
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword and parent keyword already exists in the data source.
        /// </summary>
        /// <param name="keyword">the keyword</param>
        /// <param name="parentKeyword">and the parent keyword to search for</param>
        /// <returns>true if both keyword and parent keyword already exists in the data source; otherwise false</returns>
        public bool Exists(string keyword, string parentKeyword)
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));
            parentKeyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "parentKeyword"));

            Vocab vocab       = vocabularies.Create(keyword);
            Vocab parentVocab = vocabularies.Create(parentKeyword);
            return Exists(vocab, parentVocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab and parent vocab already exists in the data source.
        /// </summary>
        /// <param name="vocab">the vocab</param>
        /// <param name="parentVocab">and the parent vocab to search for</param>
        /// <returns>true if both vocab and parent vocab already exists in the data source; otherwise false</returns>
        public bool Exists(Vocab vocab, Vocab parentVocab)
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));
            parentVocab.ThrowIfNull<ArgumentNullException>("parentVocab");
            parentVocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("parentVocab id '{0}'", parentVocab.Id)));

            return (dataManager.Count<Taxonomies>(vocab.Id, parentVocab.Id) > 0);
        }

        /// <summary>
        /// Returns list of child keywords with the specified keyword and, optionally depth to traverse.
        /// </summary>
        /// <param name="keyword">the keyword</param>
        /// <param name="depth">depth to traverse</param>
        /// <returns>list of child keywords</returns>
        public List<string> GetChildren(string keyword, int depth = 1)
        {
            return GetChildren<Taxonomies>(keyword, depth);
        }

        /// <summary>
        /// Returns list of child keywords with the specified vocab and, optionally depth to traverse.
        /// </summary>
        /// <param name="vocab">the vocab</param>
        /// <param name="depth">depth to traverse</param>
        /// <returns>list of child keywords</returns>
        public List<string> GetChildren(Vocab vocab, int depth = 1)
        {
            return GetChildren<Taxonomies>(vocab, depth);
        }

        /// <summary>
        /// Returns list of child keywords with the specified keyword and, optionally depth to traverse.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword</param>
        /// <param name="depth">depth to traverse</param>
        /// <returns>list of child keywords</returns>
        public List<string> GetChildren<TTable>(string keyword, int depth = 1)
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));

            Vocab vocab = vocabularies.Create(keyword);
            return GetChildren<TTable>(vocab, depth);
        }

        /// <summary>
        /// Returns list of child keywords with the specified vocab and, optionally depth to traverse.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the vocab</param>
        /// <param name="depth">depth to traverse</param>
        /// <returns>list of child keywords</returns>
        public List<string> GetChildren<TTable>(Vocab vocab, int depth = 1)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));

            List<TTable> data = dataManager.GetChildren<TTable>(vocab.Id, depth);
            return data.AsQueryable().Select(Taxonomies.GetName()).ToList();
        }

        /// <summary>
        /// Returns the parent keyword of the specified keyword.
        /// </summary>
        /// <param name="keyword">the keyword</param>
        /// <returns>the corresponding parent keyword if exists; otherwise empty string</returns>
        public string GetParent(string keyword)
        {
            return GetParent<Taxonomies>(keyword);
        }

        /// <summary>
        /// Returns list of parent keywords with the specified keyword and, optionally depth to traverse.
        /// </summary>
        /// <param name="keyword">the keyword</param>
        /// <param name="depth">depth to traverse</param>
        /// <returns>list of parent keywords</returns>
        public List<string> GetParents(string keyword, int depth = 1)
        {
            return GetParents<Taxonomies>(keyword, depth);
        }

        /// <summary>
        /// Returns list of parent keywords with the specified vocab and, optionally depth to traverse.
        /// </summary>
        /// <param name="vocab">the vocab</param>
        /// <param name="depth">depth to traverse</param>
        /// <returns>list of parent keywords</returns>
        public List<string> GetParents(Vocab vocab, int depth = 1)
        {
            return GetParents<Taxonomies>(vocab, depth);
        }

        /// <summary>
        /// Returns the parent keyword of the specified keyword.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword</param>
        /// <returns>the corresponding parent keyword if exists; otherwise empty string</returns>
        public string GetParent<TTable>(string keyword)
            where TTable : Taxonomies
        {
            List<string> parents = GetParents<TTable>(keyword);

            if (parents == null || parents.Count == 0)
            {
                return String.Empty;
            }
            else
            {
                return parents[0];
            }
        }

        /// <summary>
        /// Returns list of parent keywords with the specified keyword and, optionally depth to traverse.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword</param>
        /// <param name="depth">depth to traverse</param>
        /// <returns>list of parent keywords</returns>
        public List<string> GetParents<TTable>(string keyword, int depth = 1)
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));

            Vocab vocab = vocabularies.Create(keyword);
            return GetParents<TTable>(vocab, depth);
        }

        /// <summary>
        /// Returns list of parent keywords with the specified vocab and, optionally depth to traverse.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the vocab</param>
        /// <param name="depth">depth to traverse</param>
        /// <returns>list of parent keywords</returns>
        public List<string> GetParents<TTable>(Vocab vocab, int depth = 1)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));

            List<TTable> data = dataManager.GetParents<TTable>(vocab.Id, depth);
            return data.AsQueryable().Select(Taxonomies.GetName()).ToList();
        }

        /// <summary>
        /// Returns list of keywords as roots.
        /// </summary>
        /// <returns>list of keywords as roots</returns>
        public List<string> GetRoots()
        {
            return GetRoots<Taxonomies>();
        }

        /// <summary>
        /// Returns list of keywords as roots.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <returns>list of keywords as roots</returns>
        public List<string> GetRoots<TTable>()
            where TTable : Taxonomies
        {
            List<TTable> data = dataManager.GetByParentId<TTable>(0);
            return data.AsQueryable().Select(Taxonomies.GetName()).ToList();
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword is a child node, and if parentKeyword is provided, of the specified parentKeyword.
        /// </summary>
        /// <param name="keyword">the keyword</param>
        /// <param name="parentKeyword">of the parent keyword</param>
        /// <returns>true if keyword is a child node, and if parentKeyword is provided, of the specified parentKeyword; otherwise false</returns>
        public bool IsChild(string keyword, string parentKeyword = "")
        {
            return IsChild<Taxonomies>(keyword, parentKeyword);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab is a child node, and if parentVocab is provided, of the specified parentVocab.
        /// </summary>
        /// <param name="vocab">the vocab</param>
        /// <param name="parentVocab">of the parent vocab</param>
        /// <returns>true if vocab is a child node, and if parentVocab is provided, of the specified parentVocab; otherwise false</returns>
        public bool IsChild(Vocab vocab, Vocab parentVocab = null)
        {
            return IsChild<Taxonomies>(vocab, parentVocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword is a child node, and if parentKeyword is provided, of the specified parentKeyword.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword</param>
        /// <param name="parentKeyword">of the parent keyword</param>
        /// <returns>true if keyword is a child node, and if parentKeyword is provided, of the specified parentKeyword; otherwise false</returns>
        public bool IsChild<TTable>(string keyword, string parentKeyword = "")
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));

            Vocab vocab       = vocabularies.Create(keyword);
            Vocab parentVocab = null;

            if (!String.IsNullOrEmpty(parentKeyword))
            {
                parentVocab = vocabularies.Create(parentKeyword);
            }

            return IsChild<TTable>(vocab, parentVocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab is a child node, and if parentVocab is provided, of the specified parentVocab.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the vocab</param>
        /// <param name="parentVocab">of the parent vocab</param>
        /// <returns>true if vocab is a child node, and if parentVocab is provided, of the specified parentVocab; otherwise false</returns>
        public bool IsChild<TTable>(Vocab vocab, Vocab parentVocab = null)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));

            // Make sure there's only one vocab exist in the data source.
            if (!IsOne(vocab))
            {
                throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.MoreThanOneFound, String.Format("vocab id '{0}'", vocab.Id));
            }

            if (parentVocab != null)
            {
                parentVocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("parentVocab id '{0}'", vocab.Id)));
                return (dataManager.Count<TTable>(vocab.Id, parentVocab.Id) > 0) ? true : false;
            }
            
            return (dataManager.Count<TTable>(vocab.Id, 0) > 0) ? false : true;
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword exists only one in the data source.
        /// </summary>
        /// <param name="keyword">the specified keyword</param>
        /// <returns>true if the keyword only exists once in the data source</returns>
        public bool IsOne(string keyword)
        {
            return IsOne<Taxonomies>(keyword);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab exists only one in the data source.
        /// </summary>
        /// <param name="vocab">the specified vocab</param>
        /// <returns>true if the keyword only exists once in the data source</returns>
        public bool IsOne(Vocab vocab)
        {
            return IsOne<Taxonomies>(vocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword exists only one in the data source.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the specified keyword</param>
        /// <returns>true if the keyword only exists once in the data source</returns>
        public bool IsOne<TTable>(string keyword)
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));

            Vocab vocab = vocabularies.Create(keyword);
            return IsOne<TTable>(vocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab exists only one in the data source.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the specified vocab</param>
        /// <returns>true if the keyword only exists once in the data source</returns>
        public bool IsOne<TTable>(Vocab vocab)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));

            return dataManager.Count<TTable>(vocab.Id) == 1;
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword already exists as root node.
        /// </summary>
        /// <param name="keyword">the keyword to search for in root nodes</param>
        /// <returns>true if keyword already exists as root node; otherwise false</returns>
        public bool IsRoot(string keyword)
        {
            return IsRoot<Taxonomies>(keyword);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab already exists as root node.
        /// </summary>
        /// <param name="vocab">the vocab to search for in root nodes</param>
        /// <returns>true if vocab already exists as root node; otherwise false</returns>
        public bool IsRoot(Vocab vocab)
        {
            return IsRoot<Taxonomies>(vocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword already exists as root node.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword to search for in root nodes</param>
        /// <returns>true if keyword already exists as root node; otherwise false</returns>
        public bool IsRoot<TTable>(string keyword)
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));

            Vocab vocab = vocabularies.Create(keyword);
            return IsRoot<TTable>(vocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab already exists as root node.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the vocab to search for in root nodes</param>
        /// <returns>true if vocab already exists as root node; otherwise false</returns>
        public bool IsRoot<TTable>(Vocab vocab)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));

            // Make sure there's only one vocab exist in the data source.
            if (!IsOne(vocab))
            {
                throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.MoreThanOneFound, String.Format("vocab id '{0}'", vocab.Id));
            }

            return RootExists<TTable>(vocab);
        }

        /// <summary>
        /// Renames the specified keyword with the new keyword.
        /// </summary>
        /// <param name="keyword">the keyword to rename</param>
        /// <param name="newKeyword">the new keyword to use</param>
        /// <returns>true if rename is successful; otherwise false</returns>
        public bool Rename(string keyword, string newKeyword)
        {
            return Rename<Taxonomies>(keyword, newKeyword);
        }

        /// <summary>
        /// Renames the specified vocab with the new vocab.
        /// </summary>
        /// <param name="vocab">the vocab to rename</param>
        /// <param name="newVocab">the new vocab to use</param>
        /// <returns>true if rename is successful; otherwise false</returns>
        /// <remarks>the specified keyword cannot have duplicates; otherwise System.ArgumentException is thrown</remarks>
        public bool Rename(Vocab vocab, Vocab newVocab)
        {
            return Rename<Taxonomies>(vocab, newVocab);
        }

        /// <summary>
        /// Renames the specified keyword with the new keyword.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword to rename</param>
        /// <param name="newKeyword">the new keyword to use</param>
        /// <returns>true if rename is successful; otherwise false</returns>
        public bool Rename<TTable>(string keyword, string newKeyword)
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));
            newKeyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "newKeyword"));

            Vocab vocab = vocabularies.Create(keyword);
            Vocab newVocab = vocabularies.Create(newKeyword);

            return Rename<TTable>(vocab, newVocab);
        }

        /// <summary>
        /// Renames the specified vocab with the new vocab.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the vocab to rename</param>
        /// <param name="newVocab">the new vocab to use</param>
        /// <returns>true if rename is successful; otherwise false</returns>
        /// <remarks>the specified keyword cannot have duplicates; otherwise System.ArgumentException is thrown</remarks>
        public bool Rename<TTable>(Vocab vocab, Vocab newVocab)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));
            newVocab.ThrowIfNull<ArgumentNullException>("newVocab");
            newVocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("newVocab id '{0}'", newVocab.Id)));

            if (!IsOne(vocab))
            {
                throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.MoreThanOneFound, String.Format("vocab id '{0}'", vocab.Id));
            }

            using (TransactionScope transaction = new TransactionScope())
            {
                // get current node and copy parentId to new one
                TTable current  = dataManager.Get<TTable>(vocab.Id)[0];
                TTable newOne   = typeof(TTable).Instance<TTable>();
                newOne.Id       = newVocab.Id;
                newOne.ParentId = current.ParentId;

                // insert new one and delete the old one.
                dataManager.Insert<TTable, int, string>(newOne);
                dataManager.Context.GetTable<TTable>().DeleteOnSubmit(current);
                dataManager.Context.SubmitChanges();
                
                transaction.Complete();
            }

            return true;
        }

        /// <summary>
        /// Removes the specified keyword from the hierarchy and promotes its child nodes as root nodes.
        /// </summary>
        /// <param name="keyword">the keyword to remove</param>
        /// <exception cref="System.ArgumentException">keyword does not exist or there are duplicates in the hierarchy data</exception>
        public void Remove(string keyword)
        {
            Remove<Taxonomies>(keyword);
        }

        /// <summary>
        /// Removes the specified vocab from the hierarchy and promotes its child nodes as root nodes.
        /// </summary>
        /// <param name="vocab">the vocab to remove</param>
        /// <exception cref="System.ArgumentException">keyword does not exist or there are duplicates in the hierarchy data</exception>
        public void Remove(Vocab vocab)
        {
            Remove<Taxonomies>(vocab);
        }

        /// <summary>
        /// Removes the specified keyword from the hierarchy and promotes its child nodes as root nodes.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword to remove</param>
        /// <exception cref="System.ArgumentException">keyword does not exist or there are duplicates in the hierarchy data</exception>
        public void Remove<TTable>(string keyword)
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));

            Vocab vocab = vocabularies.Create(keyword);
            Remove<TTable>(vocab);
        }

        /// <summary>
        /// Removes the specified vocab from the hierarchy and promotes its child nodes as root nodes.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the vocab to remove</param>
        /// <exception cref="System.ArgumentException">keyword does not exist or there are duplicates in the hierarchy data</exception>
        public void Remove<TTable>(Vocab vocab)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));

            // vocab does not exist
            if (!Exists(vocab))
            {
                throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.NotExists, String.Format("vocab id '{0}'", vocab.Id));
            }

            // vocab linked to different node
            if (dataManager.Count<TTable>(vocab.Id) > 1)
            {
                throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.MoreThanOneFound, String.Format("node with vocab id '{0}'", vocab.Id));
            }

            using (TransactionScope transaction = new TransactionScope())
            {
                // Promote children as roots, if exist
                List<TTable> children = dataManager.GetByParentId<TTable>(vocab.Id);
                for (int i = 0; i < children.Count; i++)
                {
                    TTable child = children[i];

                    TTable newChild   = typeof(TTable).Instance<TTable>();
                    newChild.Id       = child.Id;
                    newChild.ParentId = 0;

                    // add new one and delete old one due to error "Value of member of an object of type changed. A member defining the identity of the object cannot be changed.
                    // Consider adding a new object with new identity and deleting the existing one instead."
                    dataManager.Insert<TTable, int, string>(newChild);
                    dataManager.Context.GetTable<TTable>().DeleteOnSubmit(child);
                }
                dataManager.Context.SubmitChanges();

                // Delete node
                List<TTable> root = dataManager.Get<TTable>(vocab.Id);
                dataManager.Context.GetTable<TTable>().DeleteAllOnSubmit(root);

                dataManager.Context.SubmitChanges();
                transaction.Complete();
            }
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword already exists as root node.
        /// </summary>
        /// <param name="keyword">the keyword to search for in root nodes</param>
        /// <returns>true if keyword already exists as root node; otherwise false</returns>
        public bool RootExists(string keyword)
        {
            return RootExists<Taxonomies>(keyword);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab already exists as root node.
        /// </summary>
        /// <param name="vocab">the vocab to search for in root nodes</param>
        /// <returns>true if vocab already exists as root node; otherwise false</returns>
        public bool RootExists(Vocab vocab)
        {
            return RootExists<Taxonomies>(vocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified keyword already exists as root node.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="keyword">the keyword to search for in root nodes</param>
        /// <returns>true if keyword already exists as root node; otherwise false</returns>
        public bool RootExists<TTable>(string keyword)
            where TTable : Taxonomies
        {
            keyword.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "keyword"));

            Vocab vocab = vocabularies.Create(keyword);
            return RootExists<TTable>(vocab);
        }

        /// <summary>
        /// Returns value indicating whether the specified vocab already exists as root node.
        /// </summary>
        /// <typeparam name="TTable">the table type to use in the data source</typeparam>
        /// <param name="vocab">the vocab to search for in root nodes</param>
        /// <returns>true if vocab already exists as root node; otherwise false</returns>
        public bool RootExists<TTable>(Vocab vocab)
            where TTable : Taxonomies
        {
            vocab.ThrowIfNull<ArgumentNullException>("vocab");
            vocab.Id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("vocab id '{0}'", vocab.Id)));

            return (dataManager.Count<TTable>(vocab.Id, 0) > 0);
        }

        #endregion
    }
}
